refactor: redesign webview package as @microsoft/vscode-ext-webview#766
Conversation
…Sink, BaseRouterContext)
…ain openAppWebview
…red, testing, and webview directories
…tions in README and migration manual
…mics and onboarding
…ot extension mode The redesign chose the bundled-vs-dev source layout from extensionMode === Production, but the dev server (webpack serve) emits the bundled asset name (views.js). A webpack-bundled extension running in Development (the normal F5 dev flow, IS_BUNDLE=true, DEVSERVER=true) therefore requested the tsc dev file (index.js) from the dev server and got a 404, leaving the webview blank. Restore the pre-redesign contract: a required isBundled option drives the layout; extensionMode stays CSP-only. The DocumentDB consumer passes isBundled: git commit -q -F /tmp/done-msg.txt && git push -q origin HEAD && echo pushed && git --no-pager log -1 --format='%h %s'ext.isBundle. Adds regression tests.
…ubfolder Move rpcConcurrencyLogger (host) and reportObserverError (webview), plus their tests, into src/webviews/_integration/observability/ so the reference integration folder separates the router/transport wiring from the cross-cutting observability adapters. Update imports, the two external importers, the folder README, and add observability/README.md. No runtime behavior change.
…tion 7 (_integration observability regrouping)
…5 part 1) The host-side dispatch logger (rpcConcurrencyLogger) emitted a console.log on every completed RPC via the framework's consoleProcedureLogger. That line is useful when debugging the extension but is dead weight (string formatting + host-console I/O) on a shipped, installed build where no one watches that console. Gate it behind extensionMode !== Production so dev/F5 and Test builds still log while packaged builds never execute it. The concurrency telemetry gauge stays unconditional and records in every mode, production included. The mode is read per-call: ext.context is always populated by the time an RPC fires (the webview is open, so the extension has activated) and the comparison is O(1), so there is no module-load ordering risk and no measurable added cost. The framework's consoleProcedureLogger stays the neutral, opt-in sink; the gating decision lives in the DocumentDB adapter, preserving the package boundary. Tests stub ext.context.extensionMode per case and add Test-mode (still logs) and Production (no console line, gauge still recorded) coverage. Documented as Iteration 9 in the PR-766 review; Iteration 10 (accumulating-telemetry accumulate/flush redesign, part 2) staged as not-started.
Iteration 9 — console gate (R766-P05 part 1)Following the Iteration 8 latency/load-time analysis, this commit implements part 1 of the R766-P05 follow-up. What changed — if (ext.context.extensionMode !== vscode.ExtensionMode.Production) {
consoleProcedureLogger.log(entry);
}Why — the dispatch logger fires on every completed RPC. The console line is useful when debugging the extension (F5 / "Run Extension" → Notes
Scope — this closes the console half of R766-P05. The heavier half — Validation: jest (4/4) · eslint (clean) · type-check (no errors) · prettier (unchanged). No user-facing strings. Full decision record: |
… flush (R766-P05 part 2) callWithAccumulatingTelemetry batched the telemetry emit but still ran the full callWithTelemetryAndErrorHandling wrapper (IActionContext allocation, error- handling wiring, performance.now, Object.entries copy loops, an await) on EVERY call. On hot paths (per webview RPC, per keystroke) that per-call machinery is the cost that adds up, and it runs in production where the gauge is meant to. Redesign (Option 1 from the PR-766 review): the per-call path now runs the populator synchronously against a plain TelemetrySample bag and folds the values into in-memory batch totals - no IActionContext, no await, no callWithTelemetryAndErrorHandling. The Azure pipeline is entered exactly once, on flush, with the rolled-up event. - Callback contract: (sample: TelemetrySample) => void (was (ctx: IActionContext) => T | PromiseLike<T> returning Promise<T | undefined>). Retires the TelemetryWithDistributions cast; distributions is now a first-class bag field. Every call site already void-ed the result and used a synchronous callback. - Errors never accumulate: a populator throw discards the sample and is reported once through callWithTelemetryAndErrorHandling under the same callbackId (the only remaining heavy path, and rare). - Added a finite guard in accumulate so a stray NaN/Infinity cannot poison a sum or min/max reduction. - Unchanged: auto_duration_ms distribution, dist_* rollup keys, batch/interval throttle, flushAccumulatingTelemetry, flushed event name == callbackId. Migrated all direct callers (ClustersExtension, DocumentDBShellPty x6, collectionViewRouter, rpcConcurrencyLogger) and the meterSilentCatch shorthand; meterSilentCatch callers needed no change. Tests rewritten for the sync sample-bag API, adding finite-guard and 'per-call path never enters the telemetry pipeline, only flush does' coverage. Documented as Iteration 10 in the PR-766 review. Validation: jest 2668/2668, eslint (0 errors), tsc --noEmit clean, npm run build, prettier. No user-facing strings.
Iteration 10 — accumulating-telemetry per-call cost (R766-P05 part 2)Second half of the R766-P05 follow-up. Iteration 9 gated the console line; this one removes the heavier per-op cost that ran in production. Problem — Redesign (Option 1) — the per-call path now runs the populator synchronously against a plain // before
callWithAccumulatingTelemetry('evt', (ctx) => {
(ctx.telemetry as TelemetryWithDistributions).distributions.gauge = n;
ctx.telemetry.measurements.hits = 1;
});
// after — plain bag, synchronous, void
callWithAccumulatingTelemetry('evt', (sample) => {
sample.distributions.gauge = n; // first-class field, no cast
sample.measurements.hits = 1;
});API change — callback is now Preserved semantics
Callers migrated — Tests — helper test rewritten for the synchronous sample-bag API; added a finite-guard case and a "per-call path never enters the telemetry pipeline, only flush does" case (the redesign's core guarantee). Skill check — reviewed Validation: jest 2668/2668 · eslint 0 errors (2 pre-existing unrelated Full decision record: |
…teTelemetry (R766-P06) After the Iteration 10 accumulate/flush split, the callWith... name was misleading: the callWith... convention in @microsoft/vscode-azext-utils promises 'run my work inside a managed scope that hands me a full IActionContext (telemetry + errorHandling + ui + valuesToMask) and auto-records duration/result/ errors for it.' The redesigned helper does none of that on the per-call path - it fills a plain sample bag synchronously and returns void. Rename so the name stops promising a scoped call. Behavior unchanged; rename + file move only. - callWithAccumulatingTelemetry -> accumulateTelemetry (verb 'accumulate' signals 'record a data point into a batch', not 'run my work in a scope'). - flushAccumulatingTelemetry -> flushAccumulatedTelemetry. - AccumulatingTelemetryOptions -> AccumulateTelemetryOptions. - Unchanged: TelemetrySample, AUTO_DURATION_DISTRIBUTION_KEY, meterSilentCatch. - File: src/utils/callWithAccumulatingTelemetry.ts -> accumulatingTelemetry.ts (+ .test.ts) via git mv; all importers updated, including meterSilentCatch-only consumers and the two jest.mock paths. The verb now encodes whether your callback runs: accumulateTelemetry does NOT run your work; the deferred runWithAccumulatingTelemetry (issue #777) would, so a runWith... name is honest there. Reviewed the telemetry-instrumentation skill: it does not mention the accumulating helper by any name, so no skill edit was needed. Deliverable 2 (runWithAccumulatingTelemetry, wrap-an-action variant) filed as enhancement issue #777 for future pickup; not built here. Validation: jest 2668/2668, eslint 0 errors, tsc --noEmit clean, npm run build, prettier. No user-facing strings. Documented as Iteration 11 in the PR-766 review.
Iteration 11 — accumulating-telemetry rename (R766-P06)Naming follow-up to Iteration 10. After the accumulate/flush split, the Renames
The teachable distinction — the verb now encodes whether your callback runs:
Naming the parameter Telemetry skill — reviewed Deliverable 2 deferred — the "wrap an action" variant ( Validation: jest 2668/2668 · eslint 0 errors (4 pre-existing unrelated Full record: |
✅ Code Quality Checks
This comment is updated automatically on each push. |
📦 Build Size Report
Download artifact · updated automatically on each push. |
Summary
Redesigns the webview RPC package: replaces
@microsoft/vscode-ext-react-webviewwith a new@microsoft/vscode-ext-webview(0.9.0-preview) built around four side-aware subpaths and composable primitives, migrates the extension onto it, and removes the old package.What changed
@microsoft/vscode-ext-webviewwith four subpaths:.(shared, novscode/react),./host,./webview,./reactopenWebviewfactory + options-bagWebviewController,attachTrpc(bring-your-own-panel),connectTrpc,initWebviewTrpc, and telemetry middleware bodies (telemetryMiddlewareBody/loggingMiddlewareBody) wired through a consumer-suppliedTelemetryRunneradapter.useTrpcClient()returns the client directly;useRpcEvents()exposes the event channel.createMiddleware,TelemetryContext, the olduseTrpcClienttuple return,UseTrpcClientOptions._integrationlayer and panel controllers moved onto the new package; construction-only panels now use anopenAppWebviewfactory preset (the oldWebviewControllerBaseis gone).@microsoft/vscode-ext-react-webviewpackage and all references.README.md+ADVANCED.md, plus an internal migration manual.Validation
Whole-repo lint clean, full Jest suite green (2571 tests / 146 suites), and
npm run buildgreen.Draft for review.